﻿using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using 旅游管理系统.Dtos;
using 旅游管理系统.Helper;
using 旅游管理系统.Models;
using 旅游管理系统.ResourceParamaters;
using 旅游管理系统.Services;

namespace 旅游管理系统.Controllers
{
    [Route("api/[controller]")] //路由。以 api/[controller为基础
    [ApiController]  //api/controller 的属性性定，特性。在 controller运行中传递各种元素行为声明性标签，这些元素包括类，方法，结构，枚举，组件等
    public class TouristRoutesController : ControllerBase  //继承了 controller 的父类ControllerBase,包含了控制器最基本的功能：访问 ModelsData,做数据验证，获得当前用户数据，且还提供了各种返回数据类型，如 200OK，201，401等
    {
        //数据仓库的接口的私有变量
        private ITouristRouteRepository _touristRouteRepository;

        private readonly IMapper _mapper;

        // 添加 TouristRoutesController的构建函数，然后再通过构建函数注入数据仓库服务
        public TouristRoutesController(
            ITouristRouteRepository touristRouteRepository,
            IMapper mapper
            ) //通过传入旅游仓库的接口来注入旅游仓库的实列
        {
            _touristRouteRepository = touristRouteRepository; //旅游路线数据模型通过旅游路线仓库的接口与 api控制器 绑定起来了
            _mapper = mapper;
        }

        //在 ASP.NET api 的控制器中，所有接口函数都被称为 action 函数，中文翻译为 动作，定义了 api 请求的返回方法，默认情况下 action会自动匹配控制器的路由，去处理 httpget请求。但在实际情况中一般都会给它加上 [HTTPGET] ,这样会让它看起来跟清晰
        //在 httpget函数中使用数据仓库并提取所欲旅游路线的信息
        //api/touristroutes?keyword=传入的参数
        [HttpGet] //表示处理 GET请求。 GetTouristRoutes可以获得全部的旅游路线，获得的是一组数据资源
        [HttpHead] //添加 Head的请求,跟 Get 请求类似，但是该请求没有返回值。不能提高 api 的性能，只返回 action的头部用于检索响应头部的信息，但不用传输相应主题
        public async Task<IActionResult> GetTouristRoutesAsync(
            [FromQuery] TouristRouteResourceParamaters paramaters
        //[FromQuery]string keyword,
        //string rating       //筛选的评分条件，，参数由三个 小于 lessThan,大于 largerThan,等于 equalTo,rating的参数可能会包含 小于3 lessThan3,大于2 largerThan2，等于5 equalTo5。参数可以分为两个部分，运算部分 如lessThan，数字部分 如23，2，5
        ) // 将 api/[controller映射给了函数 GetTouristRoutes
        {

            //使用 await关键词来进行异步操作，返回真正的数据，当程序执行到这一步时会进行异步等待，程序会暂时被挂起，直到数据库操作完成并且返回数据
            var touristRoutesFromRepo = await _touristRouteRepository.GetTouristRoutesAsync(paramaters.Keyword, paramaters.RatingOperator, paramaters.RatingValue); //数据来源于数据仓库
            if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
            {
                return NotFound("旅游路线不存在");
            }
            var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
            return Ok(touristRoutesDto); //http 满200的情况 ,使用 DTO来代替数据模型的输出
        }

        //使用   思想来设计一个单一资源，最佳的实现方式就是在 UIL中使用目标资源名词状态的复数形式，然后就是斜杠 Id
        // 即 api/touristroutes/{touristRouteId}。控制器的 api/[controller]会映射到前半部分 api/touristroutes，而 action函数只需要负责uil的后半部分{touristRouteId}的映射关系就可以了，所以给它添加上 [HttpGet]
        [HttpGet("{touristRouteId}", Name = "GetTouristRouteById")] //由于该参数 需要映射 UIL中的动态变量，所以需要加上 花括号{}，花括号内部填入 touristRoutesId,这个参数将会被映射到 action函数(GetTouristRouteById) 的参数中,Guid是限制参数tuoristRouteId传入值的类型
        //[HttpHead("{touristRouteId}")]
        public async Task<IActionResult> GetTouristRouteByIdAsync(Guid touristRouteId)
        {
            //在调用 _touristRouteRepository从数据库拿到数据后，我们可以对数据从模型到DTO的映射，创建一个变量 touristRouteDto 来存储数据
            //获得基于 ID的唯一的一条旅游路线
            var touristRouteFromRepo = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId);
            if (touristRouteFromRepo == null)
            {
                return NotFound($"旅游路线{touristRouteId}找不到");
            }

            #region 手动映射 AutoMapper(DTO)
            //var touristRouteDto = new TouristRouteDto()
            //{
            //    //手动映射DTO
            //    /*默认情况下，AutoMapper会自动映射两个对象中名字相同的两个字段，而那些找不到映射的字段将会被忽略，使用 NULL或使用空来代替
            //    * 也可以手动添加映射关系，
            //    */
            //    //将 touritRouteFromRepo 中的值一个一个的导入 TouristRouteDto中
            //    Id = touristRouteFromRepo.Id,
            //    Title = touristRouteFromRepo.Title,
            //    Description = touristRouteFromRepo.Description,
            //    Price = touristRouteFromRepo.OriginalPrice * (decimal)(touristRouteFromRepo.DiscountPresent ?? 1),
            //    CreateTime = touristRouteFromRepo.CreateTime,
            //    UpdateTime = touristRouteFromRepo.UpdateTime,
            //    Features = touristRouteFromRepo.Features,
            //    Fees = touristRouteFromRepo.Fees,
            //    Notes = touristRouteFromRepo.Notes,
            //    Rating = touristRouteFromRepo.Rating,
            //    TravelDays = touristRouteFromRepo.TravelDays.ToString(), //将 Model类型中枚举类型的数字转化为枚举类型的值
            //    TripType = touristRouteFromRepo.TripType.ToString(),
            //    DepartureCity = touristRouteFromRepo.DepartureCity.ToString()
            //};
            #endregion 

            var touristRouteDto = _mapper.Map<TouristRouteDto>(touristRouteFromRepo); //原始数据 touristRouteFromRepo
            return Ok(touristRouteDto); //ok代表 HTTP码200的情况
        }

        [HttpPost]  // api/touristroutes
        public async Task<IActionResult> CreateTouristRouteAsync([FromBody] TouristRouteForCreationDto touristRouteForCreationDto) //DTO是一种复杂的对象，而ASP.NET 自带了反序列化的功能，所以它会将请求的主体内容解析并且加载进入参数 touristRouteForCreationDto中，我们可以直接使用这个参数并把参数映射进入 touristRoute模型，最后通过数据仓库来添加数据写入数据库
        {
            //第一步使用 AutoMapper创建新DTO与Model的映射关系
            var touristRouteModel = _mapper.Map<TouristRoute>(touristRouteForCreationDto); //类型为 TouristRoute，需要映射到数据是touristRouteForCreationDto
            //通过使用数据仓库把这个Model添加进入数据仓库中
            _touristRouteRepository.AddTouristRoute(touristRouteModel); //数据被添加到了内存中，但数据还未被写入数据库
            //调用数据仓库进行数据库的保存
            await _touristRouteRepository.SaveAsync();
            //还需要把数据库中新建的数据 Map出来，变成 TouristRouteDto传回 api，作为数据输出
            var touristRouteToReture = _mapper.Map<TouristRouteDto>(touristRouteModel); //对于一个成功的 POST请求创建资源，返回的应该是201 eat，而不是200 OK，可以使用ASP的内建函数 CreatedAtRoute来完成这个请求
            return CreatedAtRoute(
                "GetTouristRouteById",
                new { touristRouteId = touristRouteToReture.Id },
                touristRouteToReture  //post请求成功后需要输出的数据
            );
        }

        [HttpPut("{touristRouteId}")]
        public async Task<IActionResult> UpdateTouristRouteAsync(
            [FromRoute] Guid touristRouteId,
            [FromBody] TouristRouteForUpdateDto touristRouteForUpdateDto
        )
        {
            if (!await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId))
            {
                return NotFound("旅游路线找不到");
            }
            //把DTO转化为 Model后更新数据库
            var touristRouteFromRepo = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId); //赋值给本地变量 touristRouteFromRepo
            //第一步 把 touristRouteFromRepo 所有数据都提取出来，然后映射为 DTO
            //第二步 更新这个 DTO的数据
            //第三步 把更新后的 DTO所有的数据再映射到数据模型 touristRouteFromRepo
            // 上面三个步骤可以使用 AutoMapper，使用一行代码就可以实现。
            _mapper.Map(touristRouteForUpdateDto, touristRouteFromRepo); //把需要更新的 DTO touristRouteForUpdateDto映射给数据模型touristRouteFromRepo
            //在使用这个映射前一定要先把 touristRouteForUpdateDto添加进入AutoMapper的 profile的文件里

            //更新数据库
            //在数据仓库中，我们调用的是底层的 ORM框架 Entity Framework(_touristRouteRepository)，而在 Entity Framework中数据模型 touristRouteFromRepo是根据上下文关系对象 Context来进行追踪的，
            //当我们在执行 _mapper.Map时，数据模型中的数据其实已经被修改过了，而这个时候数据模型的追踪状态也就相应的发生了变化，模型的追踪状态是由 Entity Framework上下文关系对象 Context自我管理的，
            //在最后一步，到我们执行数据库保存操作的时候模型的追踪状态就会随着Context的保存，把更新后的状态写入数据库，折一切已经被很完整的被封装在 Entity Framework里面了
            await _touristRouteRepository.SaveAsync();
            //对于更新的相应，我们只需要通过一个200级别的状态码告诉前端我们后端已经更新成功了，所以对于一个成功的PUT请求可以返回 200 OK，并且可以在返回主体中包含更新后的数据资源，或者也可以返回 204 NoContent,返回一个完全不包含任何数据的相应。
            return NoContent();
        }

        [HttpPatch("{touristRouteId}")]
        public async Task<IActionResult> partiallyUpdateTouristRouteAsync(
            [FromRoute] Guid touristRouteId,
            [FromBody] JsonPatchDocument<TouristRouteForUpdateDto> patchDocument //这里传入的不再是Dto了，而是JSON文档,需要再给项目安装个JsonPatch依赖注入的拓展框架 microsoft.aspnetcore.mvc.newt
        )
        {
            if (!await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId))
            {
                return NotFound("旅游路线找不到");
            }
            var touristRouteFromRepo = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId);
            //添加映射关系
            var touristRouteToPatch = _mapper.Map<TouristRouteForUpdateDto>(touristRouteFromRepo);
            //给原始的数据打上补丁
            patchDocument.ApplyTo(touristRouteToPatch,ModelState);
            //数据验证
            if(!TryValidateModel(touristRouteToPatch))
            {
                return ValidationProblem(ModelState); //BadRequest();
            }
            //把数据保存在数据库
            _mapper.Map(touristRouteToPatch, touristRouteFromRepo); //更新数据模型 ,输入数据为touristRouteToPatch ，输出数据为 touristRouteFromRepo
            await _touristRouteRepository .SaveAsync();

            return NoContent(); 
        }

        [HttpDelete("{touristRouteId}")]
        public async Task<IActionResult> DeleteTouristRouteAsync([FromRoute] Guid touristRouteId)
        {
            if (!await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId))
            {
                return NotFound("旅游路线不存在");
            }
            //删除资源是一个对数据库的操作
            var touristRoute = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId);
            _touristRouteRepository.DeleteTouristRoute(touristRoute);
            await _touristRouteRepository.SaveAsync();

            return NoContent();
        }

        [HttpDelete("({touristIDs})")]  //列表的形式要使用括号括起来
        public async Task<IActionResult> DeleteByIDsAsync(
            [ModelBinder(BinderType = typeof(ArrayModelBinder))][FromRoute] IEnumerable<Guid> touristIDs)
        {
            if(touristIDs == null)
            {
                return BadRequest();
            }
            var touristRoutesFromRepo = await _touristRouteRepository.GetTouristRoutesByIDListAsync(touristIDs);
            _touristRouteRepository.DeleteTouristRoutes(touristRoutesFromRepo);
            await _touristRouteRepository.SaveAsync();

            return NoContent();
        }
    }
}